
#include "MainApp.h"
#include <fstream>
#include "Poco\Util\PropertyFileConfiguration.h"
#include "Poco\RecursiveDirectoryIterator.h"
#include "Poco\Path.h"
#include "Poco\File.h"
#include "Poco\Crypto\Cipher.h"
#include "Poco\Crypto\CipherFactory.h"
#include "SrcFilesWatcher.h"

#include "Poco\Crypto\CipherKey.h"
#include "Poco\Foundation.h"
#include "Poco\FileStream.h"
#include "Poco\File.h"
#include "FileCryptoer.h"
#include "FixedCryptoer.h"
#include "LineCryptoer.h"
#include "BackupManager.h"
#include "BlobPath.h"
#include "Util.h"
#include <string>
#include <cstdio>
#include "RefactCleaner.h"
using Poco::Util::PropertyFileConfiguration;
using Poco::SiblingsFirstRecursiveDirectoryIterator;
using Poco::Path;
using Poco::File;
using Poco::Crypto::Cipher;
using Poco::Crypto::CipherKey;
using Poco::Crypto::CipherFactory;
using Poco::FileInputStream;
using Poco::FileOutputStream;


//#define MY_DEBUG
//#ifdef MY_DEBUG
//#define MY_FUNC_B(tp,func,...) tp func(__VA_ARGS__){\
//std::cout<<"enter function:"<<__func__;\
//std::cout<<" file:"<<__FILE__;\
//std::cout<<" line:"<<__LINE__<<std::endl;
//#define MY_FUNC_E std::cout<<"leave function"<<__func__<<std::endl;}
//#else
//#define MY_FUNC_B(tp,func,...) tp func(__VA_ARGS__){
//#define MY_FUNC_E }
//#endif
//MY_FUNC_B(float, abc, int a, float b)
//return a + b;
//MY_FUNC_E
//#undef MY_DEBUG



MainApp::MainApp() :_helpRequested(false), _encode(false),
_decode(false), _initRequested(false),
_cleansrc(false), _cleansrcs(false), _watchmode(false),
_backup(false), _restore(false), _restore_ver(0)
{
	
	this->setUnixOptions(true);
}


MainApp::~MainApp()
{
}
void MainApp::initialize(Application& self)
{

	loadConfiguration(); // load default configuration files, if present

	Application::initialize(self);
	// add your own initialization code here
}

void MainApp::uninitialize()
{
	// add your own uninitialization code here
	Application::uninitialize();
}

void MainApp::reinitialize(Application& self)
{
	Application::reinitialize(self);
	// add your own reinitialization code here
}

void MainApp::defineOptions(OptionSet& options)
{
	Application::defineOptions(options);

	options.addOption(
		Option("init", "i", "init default property configuration file")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleInit)));

	options.addOption(
		Option("help", "h", "display help information on command line arguments")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleHelp)));

	options.addOption(
		Option("decode", "d", "decode source files")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleDecode)));

	options.addOption(
		Option("encode", "e", "encode source files")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleEncode)));

	options.addOption(
		Option("list-backup", "lb", "list backup files")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleListBackup)));

	options.addOption(
		Option("backup", "b", "backup source files")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleBackup)));
	options.addOption(
		Option("restore", "r", "restore source files")
		.argument("TIMESTAMP",false)
	
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleRestore)));

	options.addOption(
		Option("cleansrc", "csr", "remove the deleted encrypted files from decrypt files.")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleCleanSrc)));
	options.addOption(
		Option("cleansrcs", "css", "remove the deleted decrypted files from encrypt files.")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleCleanSrcs)));
	options.addOption(
		Option("outdate", "ods", "file outdate strategy,maybe 'force','timestamp','contents','md5'.")
		.argument("value")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleOutdateStrategy)));
	options.addOption(
		Option("view", "v", "view decoded contents")
		.argument("ENCODED")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleViewDec)));
	options.addOption(
		Option("watch", "w", "watch the change of src")
		.required(false)
		.repeatable(false)
		.callback(OptionCallback<MainApp>(this, &MainApp::handleWatch)));

}

void MainApp::handleHelp(const std::string& name, const std::string& value)
{
	_helpRequested = true;
	displayHelp();
	stopOptionsProcessing();
}

void MainApp::handleDecode(const std::string& name, const std::string& value)
{
	_decode = true;
	_encode = false;
}
void MainApp::handleEncode(const std::string& name, const std::string& value)
{
	_decode = false;
	_encode = true;
}

void MainApp::handleCleanSrc(const std::string& name, const std::string& value)
{

	_cleansrc = true;

}
void MainApp::handleCleanSrcs(const std::string& name, const std::string& value)
{

	_cleansrcs = true;

}
void MainApp::handleOutdateStrategy(const std::string& name, const std::string& value)
{
	if (value == "force" || value == "f")
		_outdateStrategy = E_OUTDATE_STRATEGY::FORCE;
	else if (value == "timestamp" || value == "time" || value == "t")
		_outdateStrategy = E_OUTDATE_STRATEGY::TIMESTAMP;
	else if (value == "contents" || value == "c")
		_outdateStrategy = E_OUTDATE_STRATEGY::CONTENTS;
	else if (value == "md5" || value == "m")
		_outdateStrategy = E_OUTDATE_STRATEGY::MD5;

}

void gen_random(char *s, const int len) {
	static const char alphanum[] =
		"0123456789"
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz";
	srand((unsigned)time(NULL));
	for (int i = 0; i < len; ++i) {
		s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
	}

}


void MainApp::handleInit(const std::string& name, const std::string& value) {
	_initRequested = true;
	static std::string DHT[][7] =
		/*
		file_ext,file_mode_header,file_mode_tailer,
		line_or_fix_mode_file_header,line_or_fix_mode_file_tailer
		line_or_fix_mode_line_header,line_or_fix_mode_line_tailer
		*/
	{ {"c","/*","*/","/*","*/","/*","*/" },
	  {"cpp","/*","*/","/*","*/","/*","*/" },
	  {"cxx","/*","*/","/*","*/","/*","*/" },
	  {"js","/*","*/","/*","*/","/*","*/" },
	  {"ts","/*","*/","","","//",""},
	  {"java","/*","*/","/*","*/","/*","*/" },
	  {"scala","/*","*/","/*","*/","/*","*/" },
	  {"htm","<!--","-->","<!--","-->","<!--","-->" },
	  {"html","<!--","-->","<!--","-->","<!--","-->" }
	};
	typedef Poco::AutoPtr<AbstractConfiguration> ConfigPtr;
	PropertyFileConfiguration* p = new PropertyFileConfiguration();
	ConfigPtr cptr = ConfigPtr(p, false);
	p->setString("src", "src");
	p->setString("srcs", "srcs");
	char pwd[16];
	gen_random(pwd, 16);
	p->setString("includes_ext", "java,c,cpp,cxx,htm,html,xml,js,ts,scala");
	p->setString("pwd", std::string(pwd, 16));
	p->setString("crypto", "aes256");
	p->setString("buffer_mode", "fixed");//fixed,line,file
	p->setInt("fixed_buffer_size", 64);
	p->setInt("line_buffer_size", 2);
	p->setString("backup-root", "e:\\\\SRC_BACKUP");
	int lang_size = sizeof(DHT);
	int len1 = sizeof(DHT[0]);
	int len2 = sizeof(DHT[0][0]);
	lang_size /= len1;
	for (int i = 0; i < lang_size; i++) {
		p->setString(DHT[i][0] + ".file_mode.file_header", DHT[i][1]);
		p->setString(DHT[i][0] + ".file_mode.file_tailer", DHT[i][2]);
		p->setString(DHT[i][0] + ".line_fix_mode.file_header", DHT[i][3]);
		p->setString(DHT[i][0] + ".line_fix_mode.file_tailer", DHT[i][4]);
		p->setString(DHT[i][0] + ".line_fix_mode.line_header", DHT[i][5]);
		p->setString(DHT[i][0] + ".line_fix_mode.line_tailer", DHT[i][6]);
	}

	std::ofstream file;
	file.open("srcs.properties");
	p->save(file);
	file.close();
	stopOptionsProcessing();
}

void MainApp::handleViewDec(const std::string & name, const std::string & value)
{
	_viewmode = true;
	_viewContents = value;
}

void MainApp::handleWatch(const std::string & name, const std::string & value)
{
	_watchmode = true;
}

void MainApp::handleBackup(const std::string & name, const std::string & value)
{
	_backup = true;
}

void MainApp::handleRestore(const std::string & name, const std::string & value)
{
	if (value.empty()) {
		_restore_ver = 0;
	}
	else {
		_restore_ver = _atoi64(value.c_str());
	}
	_restore = true;
}

void MainApp::handleListBackup(const std::string & name, const std::string & value)
{
}


void MainApp::displayHelp()
{
	HelpFormatter helpFormatter(options());
	helpFormatter.setUnixStyle(true);
	helpFormatter.setCommand(commandName());
	helpFormatter.setUsage("OPTIONS");
	helpFormatter.setHeader("Crypto application to encode or decode source file for special case,\nsuch as befor pushing or after pulling your code to public \ncode repertory.");
	helpFormatter.format(std::cout);
}
std::string MainApp::readContents(const File& file) {
	FileInputStream fis(file.path());
	std::string str((std::istreambuf_iterator<char>(fis)), std::istreambuf_iterator<char>());
	fis.close();
	return str;
}
void MainApp::writeContents(const std::string& outpathname, const std::string& contents) {
	logger().information(outpathname);
	FileOutputStream fos(outpathname);
	fos << contents;
	fos.close();
}





int MainApp::main(const ArgVec& args)
{
	//Path p("E:/work/sgy/sm.sgy");
	//p.makeDirectory();
	//for (int i = 0; i < p.depth(); i++)
	//	logger().information(p[i]);
	//logger().information(p.getDevice());
	//logger().information(p.getFileName());
	//Path pp("E:/work/");
	//logger().information(Util::getRelativePath(pp,p).toString());
	//logger().information(Util::getRelativePath(Path("e:/work/"), Path("e:/work/")).toString());
	//logger().information(Path(Path("e:/work/", Path())).toString());
	//BlobPath bp("E:/JAVA_LIB/baj-seis-io/0.0.1", "src/org/baj/**/indexer/**/*.java");
	//bp.getResult();
	//return Application::EXIT_OK;


	try {
		Path p(Path(), config().getString("application.baseName") + ".properties");
		File cfile(p);
		if (cfile.exists()) {
			loadConfiguration(cfile.path());
			Util::init(*this, NULL);
			_srcPath = Path().absolute().pushDirectory(config().getString("src"));
			_srcsPath = Path().absolute().pushDirectory(config().getString("srcs"));
			_srcPathName = _srcPath.toString();
			_srcsPathName = _srcsPath.toString();
		}
		else {

			logger().information("Configure file not find, use '--init' first, '--help' for more details.");
			return Application::EXIT_OK;
		}


		Cryptoer *fc = NULL;
		if (config().getString("buffer_mode") == "file") {
			fc = new FileCryptoer();
		}
		else if (config().getString("buffer_mode") == "fixed") {
			fc = new FixedCryptoer();
		}
		else if (config().getString("buffer_mode") == "line") {
			fc = new LineCryptoer();
		}
		Util::init(*this, fc);

		if (_backup) {
			BackupManager bm;
			bm.backup();
			return Application::EXIT_OK;
		}
		else if (_restore) {
			BackupManager bm;
			bm.restore(_restore_ver);
			return Application::EXIT_OK;
		}

		if (_viewmode) {
			logger().information(fc->rawDecode(_viewContents));
			return Application::EXIT_OK;
		}
		if ((!_encode) && (!_decode) && (!_watchmode)) {
			displayHelp();
			return Application::EXIT_OK;
		}

		if (!_helpRequested && !_initRequested && !_watchmode)
		{

			OutdateStrategy * ods = NULL;
			switch (_outdateStrategy) {
			case E_OUTDATE_STRATEGY::FORCE:
				ods = new OutdateForceStrategy();
				break;
			case E_OUTDATE_STRATEGY::TIMESTAMP:
				ods = new OutdateTimeStampStrategy();
				break;
			default:
				ods = new OutdateContentsStrategy();
			}
			fc->showInfo();
			ods->showInfo();


			SiblingsFirstRecursiveDirectoryIterator end;
			std::string sourceDir = _encode ? _srcPathName : _srcsPathName;
			SiblingsFirstRecursiveDirectoryIterator rdit(sourceDir);

			while (rdit != end)
			{
				if (rdit->isFile()) {
					if (_encode) {
						if (ods->needEncode(*rdit)) {
							logger().information("updating %s\n", rdit->path());
							fc->encodeFile(*rdit);
						}
					}
					if (_decode) {
						if (ods->needDecode(*rdit))
						{
							logger().information("updating %s\n", rdit->path());
							fc->decodeFile(*rdit);
						}
					}
				}
				++rdit;
			}


			delete fc;

			logger().information("Nothing to do!\nBye~");


		}
		if (_cleansrc) {
			RefactCleaner rc;
			rc.cleanSrc();
			return Application::EXIT_OK;
		}
		if (_cleansrcs) {
			RefactCleaner rc;
			rc.cleanSrcs();
			return Application::EXIT_OK;
		}

		if (_watchmode) {
			SrcFilesWatcher sw;
			sw.watch();
			/*DirectoryWatcher dw(_srcPathName, DirectoryWatcher::DW_FILTER_ENABLE_ALL, 2);

			dw.itemAdded += delegate(this, &MainApp::onItemAdded);
			dw.itemRemoved += delegate(this, &MainApp::onItemRemoved);
			dw.itemModified += delegate(this, &MainApp::onItemModified);
	*/
			waitForTerminationRequest();
		}
	}
	catch (Poco::Exception& exc)
	{
		logger().log(exc);
	}
	return Application::EXIT_OK;
}





POCO_SERVER_MAIN(MainApp)